/*
 * Copyright 2012-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.github.soiff.mdoc.processor;

import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.tree.JCTree;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.github.soiff.mdoc.holder.Classes;
import io.github.soiff.mdoc.holder.Context;
import io.github.soiff.mdoc.holder.Primitive;
import io.github.soiff.mdoc.utils.Parser;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Annotation processor that writes meta-data file for
 * {@code @Export}.
 *
 * @author Stephane Nicoll
 * @author Phillip Webb
 * @author Kris De Volder
 * @since 1.2.0
 */
@SuppressWarnings("unchecked")
public class MarkdownProcessor extends AbstractProcessor {

    private ProcessingEnvironment pe;

    private Trees trees;

    private Parser parser;

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        pe = env;
        parser = new Parser(pe);
        trees = Trees.instance(pe);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return parser.settings.getAnnotations().keySet();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        JCTree.JCCompilationUnit cu;
        TreePath tp;
        // Annotation qualified name
        String aqn, qn;
        // Annotation Objects Map
        Map<String, Annotation> aos = new HashMap<>(32);
        // Freemaker context
        Context context = new Context(32);
        // Template
        Template template;
        // Class declaration
        final Classes classes = Classes.getInstance();
        JCTree.JCMethodDecl methodDecl;
        String comment;
        Annotation annotation;
        for (TypeElement te : annotations) {
            aqn = te.getQualifiedName().toString();
            try {
                classes.add(Class.forName(aqn));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                continue;
            }
            final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(te);
            for (Element element : elements) {
                tp = trees.getPath(element);
                cu = (JCTree.JCCompilationUnit) tp.getCompilationUnit();
                annotation = element.getAnnotation((Class<Annotation>)classes.getClass(aqn));
                if (element instanceof Symbol.ClassSymbol) {
                    qn = ((Symbol.ClassSymbol) element).getQualifiedName().toString();
                    aos.put(qn, annotation);
                    classes.add((Symbol.ClassSymbol) element);
                } else if (element instanceof Symbol.MethodSymbol) {
                    // ClassSymbol must have been added earlier.
                    template = parser.settings.getTemplate(aqn);
                    if (null != template) {
                        qn = element.getEnclosingElement().toString();
                        methodDecl = (JCTree.JCMethodDecl) tp.getLeaf();
                        context.clear();
                        methodDecl.getParameters().forEach(p -> {
                            context.addInput(new Primitive(p.getName().toString(), p.getType().toString()));
                        });
                        context.setRequest("TODO");
                        context.setReturn(new Primitive(methodDecl.getReturnType().toString()));
                        context.setResponse("TODO");
                        if (cu.docComments.hasComment(methodDecl)) {
                            comment = cu.docComments.getComment(methodDecl).getText().trim();
                            context.setTitle(comment.substring(0, comment.length() >= 32 ? 32 : comment.length()).trim());
                        } else {
                            context.setTitle(methodDecl.getName().toString());
                        }
                        context.setType(qn);
                        context.setMethod(methodDecl.getName().toString());
                        context.setVersion(parser.module.getReleaseId().getVersion());
                        context.setArtifact(parser.module.getReleaseId().toExternalForm());
                        context.setAuthor(parser.settings.getAuthor());
                        context.setDate(parser.settings.getDate());
                        context.setParent(aos.get(qn));
                        context.setSelf(annotation);
                        Writer fw = parser.getWriter(context.getTitle());
                        if (null != fw) {
                            try {
                                template.process(context, fw);
                                fw.flush();
                                fw.close();
                            } catch (TemplateException | IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }

        // Sync metadata to file.
        //if (roundEnv.processingOver()) {
        //}

        return false;
    }

    private String getStackTrace(Exception ex) {
        StringWriter writer = new StringWriter();
        ex.printStackTrace(new PrintWriter(writer, true));
        return writer.toString();
    }

    private void logWarning(String msg) {
        log(Kind.WARNING, msg);
    }

    private void log(Kind kind, String msg) {
        this.processingEnv.getMessager().printMessage(kind, msg);
    }
}
